package com.gitee.ice1938.module.admin.filemanager.fun;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;

import javax.jcr.AccessDeniedException;
import javax.jcr.Binary;
import javax.jcr.InvalidItemStateException;
import javax.jcr.InvalidSerializedDataException;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.LoginException;
import javax.jcr.MergeException;
import javax.jcr.NamespaceException;
import javax.jcr.NoSuchWorkspaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.Workspace;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.version.VersionException;

import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.jackrabbit.value.BinaryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.gitee.ice1938.module.admin.sysoption.fun.OptionChangedEvent;
import com.gitee.ice1938.module.admin.sysoption.fun.OptionService;

import sun.net.www.MimeTable;

@SuppressWarnings("restriction")
@Service
public class FileManageSvcImpl implements FileManageSvc {

	/**
	 * Logger for this class
	 */
	private static final Logger logger = LoggerFactory.getLogger(FileManageSvcImpl.class);

	private static final String def_file_manage_rep = "D://jackrabbit_rep";

	private static final String FOLDER_SEPARATOR = "/";

	@Autowired
	private OptionService optionService;

	/**
	 * 仓库
	 */
	private Repository repository = null;
	private Session session = null;

	@Override
	public String addFile(String folderName, String fileName, byte[] data, long fileLogastModified,
	        boolean isOverride) {
		// 获取Session
		Session session = getSession(null);
		String filePath = null;
		try {
			// 获取根节点
			Node root = session.getRootNode();
			// 按照文件夹进行保存文件
			String path = doPath(folderName, fileName);
			folderName = path.substring(0, path.lastIndexOf(FOLDER_SEPARATOR));
			fileName = path.substring(path.lastIndexOf(FOLDER_SEPARATOR) + 1);
			root = createFolder(session, folderName);
			if (isOverride && root.hasNode(fileName)) {
				root.getNode(fileName).remove();
			}
			// 给根节点添加一个文件节点
			Node fileNode = root.addNode(fileName, JcrConstants.NT_FILE);
			filePath = fileNode.getPath();
			// 给文件节点添加一个资源节点
			Node fileContentNode = fileNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
			MimeTable mt = MimeTable.getDefaultTable();
			String mimeType = mt.getContentTypeFor(fileName);
			if (mimeType == null) {
				mimeType = "application/octet-stream";
			}
			// 设置资源节点的MIME类型
			fileContentNode.setProperty(JcrConstants.JCR_MIMETYPE, mimeType);
			// 设置待存入文件的输入流 rmi 服务需要序列化数据
			ValueFactory vfactory = session.getValueFactory();
			Binary fileBinary = vfactory.createBinary(new BinaryImpl(data).getStream());
			// Binary fileBinary = new BinaryImpl(data);
			fileContentNode.setProperty(JcrConstants.JCR_DATA, fileBinary);
			// 设置编码
			fileContentNode.setProperty(JcrConstants.JCR_ENCODING, "UTF-8");
			// 文件修改时间
			Calendar lastModified = Calendar.getInstance();
			lastModified.setTimeInMillis(fileLogastModified);
			JcrUtils.setLastModified(fileContentNode, lastModified);
			// 保存文件
			session.save();
			// 注销Session
			closeSession(session);
		} catch (RepositoryException e) {
			logger.error(" 上传文件过程中出现异常 ", e);
			throw translateException(e);
		}

		return filePath;
	}

	private Node createFolder(Session session, String folderName) throws RepositoryException {
		// 获取Session
		folderName = StringUtils.cleanPath(folderName);

		Node node;
		String[] folders = folderName.split(FOLDER_SEPARATOR);
		Workspace work = session.getWorkspace();
		node = work.getSession().getRootNode();
		// 获取上传文件的folderId
		for (String folder : folders) {
			if (node.hasNode(folder)) {
				node = node.getNode(folder);
				continue;
			}
			node = node.addNode(folder, JcrConstants.NT_FOLDER);
		}
		return node;
	}

	/**
	 * 按Fileid删除文件
	 * 
	 * @param fileId
	 *            保存文件时拿到的FileId
	 * @return
	 */
	public void delete(String relPath) {

		// 获取Session
		Session session = getSession(null);

		try {
			if(relPath.startsWith("/")) {
				relPath = relPath.substring(1);
			}
			// 按照FileId取出符合条件的节点
			Node fileNode = session.getRootNode().getNode(relPath);
			fileNode.remove();
			session.save();
			// 注销Session
			closeSession(session);
		} catch (RepositoryException e) {
			logger.error("删除过程中出错", e);
			throw translateException(e);
		}
	}

	public FileNode readFile(String folderName, String fileName) {
		// 获取Session
		Session session = getSession(null);
		try {
			folderName = doPath(folderName, fileName);
			Node fileNode = session.getRootNode().getNode(folderName);
			FileNode node = readFile(fileNode);
			closeSession(session);
			return node;
		} catch (RepositoryException e) {
			logger.error("获取文件失败", e);
			throw translateException(e);
		}
	}

	private String doPath(final String folderName, final String fileName) {
		String path = Paths.get(folderName, fileName).toString();
		return StringUtils.cleanPath(path);
	}

	@Override
	public FileNode readFile(String relPath) {
		// 获取Session
		Session session = getSession(null);

		try {
			if(relPath.startsWith("/")) {
				relPath = relPath.substring(1);
			}
			//Node fileNode = session.getNodeByIdentifier(fileId);
			Node fileNode = session.getRootNode().getNode(relPath);
			FileNode node = readFile(fileNode);
			closeSession(session);
			return node;
		} catch (RepositoryException e) {
			logger.error("获取文件失败", e);
			throw translateException(e);
		}
	}

	private FileNode readFile(Node fileNode) throws PathNotFoundException, RepositoryException, ValueFormatException {
		Node fileContentNode = fileNode.getNode(JcrConstants.JCR_CONTENT);
		// IO
		Binary data = fileContentNode.getProperty(JcrConstants.JCR_DATA).getBinary();
		// 文件类型
		String fileType = fileContentNode.getProperty(JcrConstants.JCR_MIMETYPE).getString();
		// 文件名
		String fileName = fileNode.getName();
		// 修改时间
		Long lastModified = fileContentNode.getProperty(JcrConstants.JCR_LASTMODIFIED).getLong();
		return new FileNode(fileNode.getIdentifier(), fileName, fileType, lastModified, data);
	}

	@Override
	public void init() {
		try {
			String value = optionService.findOption(FileManageSvc.KEY_FILE_MANAGE_REP);
			if (value == null) {
				// Set<PosixFilePermission> perms =
				// PosixFilePermissions.fromString("rw-rw-rw-");
				// FileAttribute<?> attrs =
				// PosixFilePermissions.asFileAttribute(perms);

				if (!Files.exists(Paths.get(def_file_manage_rep))) {
					Files.createDirectory(Paths.get(def_file_manage_rep));
				}

				value = "file:///" + def_file_manage_rep;
				optionService.initOption(FileManageSvc.KEY_FILE_MANAGE_REP, value, "文件仓库路径", "文件保存根路径");
			}
			// file:///D://jackrabbit_rep
			repository = JcrUtils.getRepository(value);
			String name = repository.getDescriptor(Repository.REP_NAME_DESC);
			logger.info("{} repository is created from url is {}", name, value);
		} catch (RepositoryException e) {
			logger.error("<no args>", e);
			throw translateException(e);
		} catch (IOException e) {
			logger.error("<no args>", e);
			throw translateException(e);
		}

	}

	/**
	 * <p>
	 * 关闭session
	 * </p>
	 * 
	 * @param session
	 */
	private void closeSession(Session session) {

		if (null != session) {
			// session.logout();
		}
	}

	/**
	 * 获取Session
	 * 
	 * @param workspace
	 * 
	 * @return
	 */
	private Session getSession(String workspace) {

		// Session session = null;
		try {
			if (null == repository) {
				init();
			}
			if (session == null) {
				session = repository.login(new SimpleCredentials("admin", "admin".toCharArray()), workspace);
			}
		} catch (RepositoryException e) {
			logger.error("<no args>", e);
			throw translateException(e);
		}
		return session;
	}

	@EventListener
	public void eventListener(OptionChangedEvent event) throws IOException, RepositoryException {
		if (FileManageSvc.KEY_FILE_MANAGE_REP.equals(event.getSource())) {
			init();
		}
	}

	/**
	 * Jcr exception translator - it converts specific JSR-170 checked
	 * exceptions into unchecked Spring DA exception.
	 * 
	 * @param ex
	 * @return
	 */
	public static DataAccessException translateException(IOException ex) {
		return new DataAccessResourceFailureException("I/O failure", ex);
	}

	/**
	 * Jcr exception translator - it converts specific JSR-170 checked
	 * exceptions into unchecked Spring DA exception.
	 * 
	 * @author Guillaume Bort <guillaume.bort@zenexity.fr>
	 * @author Costin Leau
	 * 
	 * @param ex
	 * @return
	 */
	public static DataAccessException translateException(RepositoryException ex) {
		if (ex instanceof AccessDeniedException) {
			return new DataRetrievalFailureException("Access denied to this data", ex);
		}
		if (ex instanceof ConstraintViolationException) {
			return new DataIntegrityViolationException("Constraint has been violated", ex);
		}
		if (ex instanceof InvalidItemStateException) {
			return new ConcurrencyFailureException("Invalid item state", ex);
		}
		if (ex instanceof InvalidQueryException) {
			return new DataRetrievalFailureException("Invalid query", ex);
		}
		if (ex instanceof InvalidSerializedDataException) {
			return new DataRetrievalFailureException("Invalid serialized data", ex);
		}
		if (ex instanceof ItemExistsException) {
			return new DataIntegrityViolationException("An item already exists", ex);
		}
		if (ex instanceof ItemNotFoundException) {
			return new DataRetrievalFailureException("Item not found", ex);
		}
		if (ex instanceof LoginException) {
			return new DataAccessResourceFailureException("Bad login", ex);
		}
		if (ex instanceof LockException) {
			return new ConcurrencyFailureException("Item is locked", ex);
		}
		if (ex instanceof MergeException) {
			return new DataIntegrityViolationException("Merge failed", ex);
		}
		if (ex instanceof NamespaceException) {
			return new InvalidDataAccessApiUsageException("Namespace not registred", ex);
		}
		if (ex instanceof NoSuchNodeTypeException) {
			return new InvalidDataAccessApiUsageException("No such node type", ex);
		}
		if (ex instanceof NoSuchWorkspaceException) {
			return new DataAccessResourceFailureException("Workspace not found", ex);
		}
		if (ex instanceof PathNotFoundException) {
			return new DataRetrievalFailureException("Path not found", ex);
		}
		if (ex instanceof ReferentialIntegrityException) {
			return new DataIntegrityViolationException("Referential integrity violated", ex);
		}
		if (ex instanceof UnsupportedRepositoryOperationException) {
			return new InvalidDataAccessApiUsageException("Unsupported operation", ex);
		}
		if (ex instanceof ValueFormatException) {
			return new InvalidDataAccessApiUsageException("Incorrect value format", ex);
		}
		if (ex instanceof VersionException) {
			return new DataIntegrityViolationException("Invalid version graph operation", ex);
		}
		// fallback
		return new JcrSystemException(ex);
	}

	@Override
	public List<FileNode> list(String workspace, String relPath) {
		// 获取Session
		Session session = getSession(workspace);
		List<FileNode> listName = new ArrayList<FileNode>();
		try {
			// 获取根节点
			Node root = session.getRootNode();
			if (relPath != null && !relPath.isEmpty()) {
				root = root.getNode(relPath);
			}

			NodeIterator node = root.getNodes();
			for (Iterator<Node> iterator = node; iterator.hasNext();) {
				Node note_ = iterator.next();
				String primaryNodeType = note_.getPrimaryNodeType().getName();
				if (primaryNodeType.equals(JcrConstants.NT_RESOURCE)) {
					listName.add(readFile(note_.getParent()));
				} else if (primaryNodeType.equals(JcrConstants.NT_FILE)
				        || primaryNodeType.equals(JcrConstants.NT_FOLDER)) {
					listName.add(new FileNode(note_.getIdentifier(), note_.getPath()));
				}
			}
			closeSession(session);
		} catch (RepositoryException e) {
			logger.error("<no args>", e);
			throw translateException(e);
		}

		return listName;
	}
}
